//
// Created by cmj on 2020/7/10.
//
#include <vector>
#include <sstream>
#include <iostream>
#include <cstring>
#include <algorithm>
#include "erlua_wrap.h"

void erlua_wrap::clean() const {
    if (state) {
        int element_nums = lua_gettop(state);
        if (element_nums > 0) {
            lua_pop(state, element_nums);
        }
    }
}

void erlua_wrap::push_param_from(ErlNifEnv *env, ERL_NIF_TERM term) const {
    if (!state) {
        throw erlua_exception("no state");
    }
    int tab_idx, i;
    int integer = 0;
    unsigned int uint = 0;
    unsigned long ul = 0;
    int64_t i64 = 0;
    uint64_t ui64 = 0;
    switch (enif_term_type(env, term)) {
        case ERL_NIF_TERM_TYPE_ATOM:
            if (!enif_get_atom(env, term, this->temp_buf, ERLUA_MAX_STR_LEN, ERL_NIF_LATIN1)) {
                throw erlua_exception("atom to long");
            }
            lua_pushstring(state, this->temp_buf);
            break;
        case ERL_NIF_TERM_TYPE_BITSTRING:
            ErlNifBinary b;
            if (!enif_inspect_binary(env, term, &b)) {
                throw erlua_exception("not a binary");
            }
            if (b.size + 1 > ERLUA_MAX_STR_LEN) {
                throw erlua_exception("binary is too bing!");
            }
            void *ptr;
            ptr = memcpy(this->temp_buf, b.data, b.size);
            if (ptr) {
                this->temp_buf[b.size] = '\0';
            } else {
                throw erlua_exception("unexpected error!");
            }
            lua_pushstring(state, this->temp_buf);
            break;
        case ERL_NIF_TERM_TYPE_FLOAT:
            lua_Number f;
            enif_get_double(env, term, &f);
            lua_pushnumber(state, f);
            break;
        case ERL_NIF_TERM_TYPE_FUN:
            throw erlua_exception("erlang func not support!");
        case ERL_NIF_TERM_TYPE_INTEGER:
            if (enif_get_int(env, term, &integer)) {
                lua_pushinteger(state, integer);
            } else if (enif_get_uint(env, term, &uint)) {
                lua_pushinteger(state, uint);
            } else if (enif_get_int64(env, term, &i64)) {
                lua_pushinteger(state, i64);
            } else if (enif_get_ulong(env, term, &ul)) {
                lua_pushinteger(state, ul);
            } else if (enif_get_uint64(env, term, &ui64)) {
                lua_pushinteger(state, ui64);
            } else {
                throw erlua_exception("not support int size");
            }
            break;
        case ERL_NIF_TERM_TYPE_LIST:
            unsigned int ls_len;
            if (enif_get_list_length(env, term, &ls_len)) {
                // 通常情况下list长度不会比 INT_MAX 大
                lua_createtable(state, static_cast<int>(ls_len), 0);
            } else {
                lua_createtable(state, ERLUA_DEFAULT_SIZE_HINT, 0);
            }
            tab_idx = lua_gettop(state);    // table 在 stack 中的 index
            ERL_NIF_TERM head_element;
            ERL_NIF_TERM tail_elements;
            i = 1; // 注意下标从1开始
            while (enif_get_list_cell(env, term, &head_element, &tail_elements)) {
                push_param_from(env, head_element);
                lua_rawseti(state, tab_idx, i);
                i++;
                term = tail_elements;
            }
            break;
        case ERL_NIF_TERM_TYPE_MAP:
            size_t map_size;
            if (enif_get_map_size(env, term, &map_size)) {
                throw erlua_exception("get map size fail.");
            }
            lua_createtable(state, 0, static_cast<int>(map_size));
            tab_idx = lua_gettop(state);    // table 在 stack 中的 index
            ERL_NIF_TERM key, value;
            ErlNifMapIterator iter;
            enif_map_iterator_create(env, term, &iter, ERL_NIF_MAP_ITERATOR_FIRST);
            while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
                push_param_from(env, key);
                push_param_from(env, value);
                lua_settable(state, tab_idx);
                enif_map_iterator_next(env, &iter);
            }
            enif_map_iterator_destroy(env, &iter);
            break;
        case ERL_NIF_TERM_TYPE_PID:
            ErlNifPid *pid_ptr;
            pid_ptr = static_cast<ErlNifPid *>(lua_newuserdata(state, sizeof(ErlNifPid)));
            if (!enif_get_local_pid(env, term, pid_ptr)) {
                throw erlua_exception("Only local pid is supported!", term);
            }
            luaL_setmetatable(state, ERLUA_NIF_PID_META);
            break;
        case ERL_NIF_TERM_TYPE_PORT:
            throw erlua_exception("Port not support!");
        case ERL_NIF_TERM_TYPE_REFERENCE:
            throw erlua_exception("Reference not support!");
        case ERL_NIF_TERM_TYPE_TUPLE:
            int terms_nums;
            const ERL_NIF_TERM *terms_ptr;
            if (!enif_get_tuple(env, term, &terms_nums, reinterpret_cast<const ERL_NIF_TERM **>(&terms_ptr))) {
                throw erlua_exception("Read tuple fail!");
            }
            lua_createtable(state, terms_nums, 0);
            tab_idx = lua_gettop(state);    // table 在 stack 中的 index
            for (int j = 0; j < terms_nums; ++j) {
                push_param_from(env, terms_ptr[j]);
                lua_rawseti(state, tab_idx, j + 1);   // 注意下标从1开始
            }
            break;
        case ERL_NIF_TERM_TYPE__MISSING_DEFAULT_CASE__READ_THE_MANUAL:
            throw erlua_exception("should never happen!");
        default:
            throw erlua_exception("Unsupported type!");

    }
}

void erlua_wrap::push_param_from_ls(ErlNifEnv *env, ERL_NIF_TERM list) const {
    if (!enif_is_list(env, list)) {
        throw erlua_exception("third param not a list");
    }
    ERL_NIF_TERM head;
    ERL_NIF_TERM tail = list;
    while (enif_get_list_cell(env, list, &head, &tail)) {
        push_param_from(env, head);
        list = tail;
    }
}

void erlua_wrap::fetch_string_from_term(ErlNifEnv *env, ERL_NIF_TERM term) const {
    auto termType = enif_term_type(env, term);
    switch (termType) {
        case ERL_NIF_TERM_TYPE_ATOM:
            if (!enif_get_atom(env, term, this->temp_buf, ERLUA_MAX_STR_LEN, ERL_NIF_LATIN1)) {
                throw erlua_exception("atom to long");
            }
            break;
        case ERL_NIF_TERM_TYPE_BITSTRING:
            ErlNifBinary b;
            if (!enif_inspect_binary(env, term, &b)) {
                throw erlua_exception("not a binary");
            }
            if (b.size + 1 > ERLUA_MAX_STR_LEN) {
                throw erlua_exception("binary is too bing!");
            }
            void *ptr;
            ptr = memcpy(this->temp_buf, b.data, b.size);
            if (ptr) {
                this->temp_buf[b.size] = '\0';
            } else {
                throw erlua_exception("unexpected error!");
            }
            break;
        case ERL_NIF_TERM_TYPE_LIST:
            if (!enif_get_string(env, term, this->temp_buf, ERLUA_MAX_STR_LEN, ERL_NIF_LATIN1)) {
                throw erlua_exception("string to long");
            }
            break;
        default:
            throw erlua_exception("param is not a string or atom!Term type:" + std::to_string(termType) + "\n");
    }
}

void erlua_wrap::load_file() const {
    int ecode = luaL_loadfile(this->state, this->temp_buf);
    if (ecode != LUA_OK) {
        throw erlua_exception("load file fail", erlua_ecode(ecode));
    }
}

void erlua_wrap::fetch_global_func() const {
    if (lua_getglobal(this->state, this->temp_buf) != LUA_TFUNCTION) {
        std::ostringstream ss;
        ss << "global func" << this->temp_buf << "is not exist!" << std::endl;
        throw erlua_exception(ss.str());
    }
}

void erlua_wrap::pcall_func(int nargs, int nresults) const {
    int ecode = lua_pcall(this->state, nargs, nresults, 0);
    if (ecode != LUA_OK) {
        const char *err_str = lua_tostring(this->state, -1);
        if (err_str == nullptr) {
            throw erlua_exception("fail but non string error object is not support");
        } else {
            std::ostringstream ss;
            throw erlua_exception(err_str);
        }
    }
}

ERL_NIF_TERM erlua_wrap::pop_params_to(ErlNifEnv *env) {
    std::vector<ERL_NIF_TERM> ret_terms;

    while (lua_gettop(state) != 0) {
        ret_terms.emplace_back(this->get_term_from_index(env, -1));
        lua_pop(state, 1);
    }
    if (ret_terms.empty()) {
        return erlua_global.nil;
    } else if (ret_terms.size() == 1) {
        return ret_terms.at(0);
    }
    std::reverse(ret_terms.begin(), ret_terms.end());
    return enif_make_tuple_from_array(env, ret_terms.data(), ret_terms.size());
}

ERL_NIF_TERM erlua_wrap::get_term_from_index(ErlNifEnv *env, int idx) {    // idx=-1
    idx = lua_absindex(state, idx);     // 如果用相对index,table遍历的时候会dump!因为栈元素个数会变
    std::vector<ERL_NIF_TERM> tab_terms;
    switch (lua_type(state, idx)) {
        case LUA_TNONE:
            throw erlua_exception("non-valid (but acceptable) index!");
        case LUA_TNIL:
            return erlua_global.nil;
        case LUA_TBOOLEAN:
            if (lua_toboolean(state, idx)) {
                return erlua_global.atom_true;
            } else {
                return erlua_global.atom_false;
            }
        case LUA_TLIGHTUSERDATA:
            throw erlua_exception("light userdata is not support yet");
        case LUA_TNUMBER:
            if (lua_isinteger(state, idx)) {
                lua_Integer integer;
                integer = lua_tointeger(state, idx);
                if (integer > -2147483648 && integer < 2147483648) {
                    return enif_make_int(env, integer);      
                } else {
                    return enif_make_int64(env, integer); 
                } 
            } else {
                lua_Number number;
                number = lua_tonumber(state, idx);
                return enif_make_double(env, number);
            }
        case LUA_TSTRING:
            const char *str;
            str = lua_tostring(state, idx);
            return enif_make_string(env, str, ERL_NIF_LATIN1);
        case LUA_TTABLE:
            /* table is in the stack at index 't' */
            lua_pushnil(state);  /* first key */
            ERL_NIF_TERM key_term;
            ERL_NIF_TERM value_term;
            while (lua_next(state, idx) != 0) {
                /* uses 'key' (at -2) and 'value' (at -1) */
                key_term = this->get_term_from_index(env, -2);
                value_term = this->get_term_from_index(env, -1);
                tab_terms.emplace_back(enif_make_tuple2(env, key_term, value_term));
                lua_pop(state, 1);
                // ERLUA_STACK(state);
            }
            return enif_make_list_from_array(env, tab_terms.data(), tab_terms.size());
        case LUA_TFUNCTION:
            throw erlua_exception("functions can not be the return values to erlang!");
        case LUA_TUSERDATA:
            void *user_data_ptr;
            user_data_ptr = luaL_checkudata(state, -1, ERLUA_NIF_PID_META);
            if (user_data_ptr) {
                return enif_make_pid(env, static_cast<ErlNifPid *>(user_data_ptr));
            }
            throw erlua_exception("userdata can not recognize!");
        case LUA_TTHREAD:
            throw erlua_exception("threads can not be the return values to erlang!");
        default:
            throw erlua_exception("not supported type!;");
    }
}
